All Articles

Python으로 SSL 인증서 정보 추출 및 검증

Cryptography 를 이용한 인증서 정보 추출


Cryptography 모듈을 호출하여 인증서 내용을 불러오는 것으로 시작한다.

  • 아래의 loadpemx509_certificate()를 통해서 인증서의 정보를 가져올 수 있다.

    from cryptography import x509
    from cryptography.hazmat.backends import default_backend
    
    cert_pem = "인증서의 내용"
    cert = x509.load_pem_x509_certificate(cert_pem, default_backend())
  • 인증서 Version 확인

    # 위의 코드에서부터 연결되어 내려온다.
    cert.version
  • 인증서 Fingerprint

    from cryptography.hazmat.primitives import hashes
    
    # 암호화 타입에 따라서 fingerprint의 정보는 달라짐., 
    # browser에서 인증서 정보를 보면 fingerprint의 암호화 타입도 같이 확인이 됨 
    fingerprint = cert.fingerprint(hashes.SHA1())
    fingerprint = fingerprint.encode('hex')
  • serial_number

    # 인증서의 serial number 를 가져온다
    cert.serial_number
    
    # 단, browser에서 표시되는 인증서 정보에서의 값과 유사하게 만들어 주기 위해서는 아래의 추가 작업이 필요하다.
    serial = hex(cert.serial_number)
    serial = serial.rstrip("L").lstrip("0x")
    serial = serial.zfill(34)
    serial_list = [serial[s:s+2] for s in range(0, len(serial), 2)]
  • 인증서 등록일자

    cert.not_valid_before
  • 인증서 만료일자

    cert.not_valid_after
  • 인증서의 도메인 추출

    from cryptography.x509.oid import NameOID
    
    cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
  • 인증서 발급기관 추출

    from cryptography.x509.oid import NameOID
    
    cert.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
  • public key 추출

    from cryptography.hazmat.primitives import serialization
    
    # 인증키를 통해서 public-key를 추출한다.
    public_key = cert.public_key()
    pem = public_key.public_bytes(
    	encoding=serialization.Encoding.PEM,
    	format=serialization.PublicFormat.SubjectPublicKeyInfo
    )
  • Private key 인증서를 가져오는 방법

    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import serialization
    
    
    private_key_pem = "개인 키 내용"
    private_key = serialization.load_pem_private_key(
    	private_key_pem,
        password=None,
    	backend=default_backend()
    )

OpenSSL 모듈을 이용한 인증서 검증


OpenSSL 모듈을 이용하여 인증서와 개인키의 유효성 검증 및 체인 키와 인증서의 유효성 검증을 진행한다.

  • 기본적인 모듈 호춢 및 인증서들에 대한 변수선언을 아래와 같이 해준다.

    import OpenSSL
    
    certificate = "인증서 내용"
    private_key = "개인 키 내용"
    chain_auth_key = "체인키 내용"
  • 인증서와 개인 키간의 검증 과정

    # 개인키와 인증서를 OpenSSL의 객체로 생성
    pk_obj = OpenSSL.crypto.load_privatekey(crypto.FILETYPE_PEM, private_key)
    cert_obj = OpenSSL.crypto.load_certificate(crypto.FILETYPE_PEM, certificate)
    
    # Context 객체에 인증키와 개인키를 입력 한 후 check
    context = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
    context.use_privatekey(pk_obj)
    context.use_certificate(cert_obj)
    
    # 인증서와 개인키가 서로 유효한 경우 error 발생하지 않음
    try:
    	context.check_privatekey()
    except OpenSSL.SSL.Error as err:
    	print("do not match")

일부 인증서의 경우 인증서를 발급받은 기관에 따라서 올바르지 않은 인증서라고 나오는 경우가 있는데, 이러한 문제를 해결하기 위해서 체인 인증서를 추가로 등록한다. 아래의 경우는 그 체인 인증서가 올바른지 인증하기 위한 검토 logic 이다. 해당 내용은 해당 링크를 참조하였다.

  • 인증서와 Chain Key간의 검증

    import re
    
    # Chain Key의 경우 여러개의 인증서 내용이 연달아 있으므로 인증서들의 유효성 및 List 형태로 분리을 위한 
    # regular expression을 이용하여 처리
    PEM_RE = re.compile(b'-----BEGIN CERTIFICATE-----\r?.+?\r?-----END CERTIFICATE-----\r?\n?', re.DOTALL)
    
    # Chain Key에서 인증서내용들을 List 타입으로 분리
    def parse_chain(chain):
        return [c.group() for c in PEM_RE.finditer(chain)]
    
    store = OpenSSL.crypto.X509Store()
    try:
    	# 분리한 key 값들을 store에 저장.
    	# store에 인증서 등록시에 인증서의 방식(type, encode)이 잘못된 경우 error 발생
    	for cr in parse_chain(chain_auth_key):
    	  store.add_cert(OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cr))
    except OpenSSL.crypto.Error as err:
    	print("Encode Type Error")
    
    # store에 등록된 인증서들과 certificate를 비교하기 위한 context 객체 생성
    context = OpenSSL.crypto.X509StoreContext(store, cert_obj)
    try:
    	# verify_certificate method를 이용하여 유효성 검증
    	context.verify_certificate()
    except OpenSSL.crypto.X509StoreContextError as err:
    	print("unable to get local issuer certificate")